Полиморфизм параметров в OCaml Первое, что делает человек, который дочитал любую книгу по Хаскелю хоты бы до третьей страницы, при изучении других типизированных языков программирования -- это пытается воспроивести классы типов. Классы типов -- это не только простой и элегантный способ параметрического полиморфизма, который позволяет писать обобщенные функции, но подход к проэктированию очень сложных систем и который вообще покрывает все виды полиморфизмов, он тесно связан с системой вывода типов и не имеющий аналогов в других языках программирования. Я написал это предложение во избежание иллюзий, что классы типов Хаскеля можно как-то сэмулировать или заменить чем-то в других языках програиммирования. Единственное исключение это ML: сигнатуры ML можно считать классами типов, структуры ML -- их экземплярами, параметризированные модули ML -- параметризированные определения классов типов или их экземпляров, а с ипользованием библиотеки deriving различия между OCaml параметризированными модулями и классами типов Haskell вообще сводятся к минимуму. Модули OCaml отличаются от классов типов тем, что классы типов обобщают вывод типов и тесно с ним связаны, а модули призваны служить для абстрагирования данных и модульности программ (как и ООП расширение). Но сейчас попробуем сделать без применения матана, в следующем посте будет матан. Цель Этот пост для тех кто как и я, начал учить OCaml и хочет написать функцию show которая бы для параметров разных типов возвращала строку, пытаясь использовать разные виды полиморфизмов в ОКамал. Модули Система типов OCaml поддерживает параметризированные модули (преобразоватлеями одних систем типов в другие). Однако полностью повторить выразительнось Хаскелля здесь проблематично: написать show 3 и show "3" что бы в обоих случаях оно выдавало строку не получается. Максимум, что можно сделать это следующее: module type Show = sig type a val show : a -> string end;; module Int : Show with type a = int = struct type a = int let show = string_of_int end;; module String : Show with type a = string = struct type a = string let show x = x end;; let x : int = let module Z = Int in Z.show 3;; let y : string = let module Z = String in Z.show "string";; print_string x ;; print_string y ;; Здесь вы видите Z.show 3 но тольно в контексте объявления, а это значит что как бы вы не изворачивались тип вам придется указать на этапе компиляции возле передаваемого значения (параметром, рядом в кортеже, закодировав битами, -- в интернете существует масса подходов которые далеки до элегантности). Кроме того будет очень сложно настолько же просто выразить скажем модули типов Int и String с поддержкой Tapeable, Eq и других типов классов (множественная поддержка других классов типов). Все остальное здесь остается в силе, на OCaml можно легко писать обобщенные типы классов зависящие от других типов классов и даже нескольких. Параметризированные модули в OCaml с точностью до ключевых слов соотвествуют стандарту языка Standard ML. Объекты Это кажется забавным но если записать класс типов буквально в OCaml наоборот получится объектный полиморфизм OCaml знакомый всем ООП программистам: class type printable = object method show : unit -> string end ;; class int x = object ( self : #printable) method show() = string_of_int x end ;; class string y = object ( self : #printable) method show() = y end ;; let x = new int 0 ;; let y = new sting "string" ;; print_string (x#show()) ;; print_string (y#show()) ;; Тут вы сможете вызвать функцию show полиморфно, но ценой включения таблицы виртуальных методов в типы и поддержки этого дела рантаймом, а не выводом типов на этапе компиляции. К сожалению придется признать, что для OCaml не существует элегантного способа реализации классов типов такого же как существует в Haskell, но система модулей OCaml достаточная что бы писать шаблонный и абстрактный код поддерживая абстрактные типы в модулях и функторы на множестве модулей. Боксинг Надо сказать, что ограничение по типу для параметров функции присутсвует как и в Хаскеле show :: (Numb a) => a -> string -- Haskell val show : 'a numb -> 'a -> string = (* OCaml *) Поэтому самый краткий и наиболее естественный путь на окамле для задачи будет создать боксинг значения и передавать этот словарь как параметр, что и делает Haskell с помощью вывода типов: type 'a numb = { box: 'a; show : 'a -> string } ;; let show numb = numb.show numb.box ;; let mkint x = { box = x ; show = string_of_int } ;; let mkstr y = { box = y ; show = function x -> x };; print_string (show (mkint 3)) ;; print_string (show (mkstr "120")) ;; deriving Едиснтвенная библиотека позволяющая писать deriving расширения поддержки многих сигнатур, которая существует в OCaml и является ситтаксическим расширением к нему -- это deriving (о ней я узнал, когда смотрел как работает Ocsigen). Используя ее можно например написать так: module type Show = sig type a val show : a -> string end type name = int deriving (Eq, Show, Tapeable) но все равно придется указывать тип let tuples_list = [(1,[1;2]); (2, []); 3, [1;2;3;4]] Show.show<(int * int list) list> tuples_list Зато, вы сможете использовать много сигнатур одновременно указывая через запятую. Библиотека написано довольно красиво и является хорошим примером расширяемости синтаксиса OCaml, с помощью которой можно разрабатыать более красивые и расширяемые системы типов в OCaml. Вывод Я попробовал написать простую функцию show на OCaml и понял наконец всю мощь классов типов языка Хаскель. До этого было впечатление, что круто, но не так понятно на сколько :-) По ходу решения задачи нашел неплохую библиотеку deriving которая позволяет создавать типы поддерживающие несколько модульных сигнатур и эмулирующая Хаскелевский deriving. _______________ [1]. https://ocaml.janestreet.com/?q=node/37 [2]. http://www.haskell.org/pipermail/haskell/2004-August/014463.html [4]. http://alaska-kamtchatka.blogspot.com/2011/09/higher-order-fun.html [6]. http://okmij.org/ftp/ML/index.html [7]. http://code.google.com/p/deriving/wiki/Introduction